import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { Scene } from '@babylonjs/core/scene';
import { AdvancedDynamicTexture } from '@babylonjs/gui/2D/advancedDynamicTexture';
import { Control } from '@babylonjs/gui/2D/controls/control';
import { Utils } from '../../tool/utils';
import { StackPanel } from '@babylonjs/gui/2D/controls/stackPanel';
import { TextBlock } from '@babylonjs/gui/2D/controls/textBlock';
import { ANCHOR, AbstractBase, IAbstractBaseOptions } from './abstract-base';
import { Helper } from '../../tool/helper';
import { BabylonContainer } from '../../base/babylon-container';


/**
 * 控件抽象类参数对象
 * @param width 宽度
 * @param height 高度
 * @param alpha 透明度
 * @param canvasSize 画布尺寸（不要太大）
 * @param inoperable 不可交互（网格的isPickable设为false，可提高性能）
 * @param offsetWithoutProportion 定位尺寸不按比缩放（即用真实三维尺寸）
 * @param useSmallestIdeal 使用最小尺寸方向为自适应尺寸（横向或纵向）
 */
interface IAbstractControlOptions extends IAbstractBaseOptions {
    width?: number,
    height?: number,
    alpha?: number,
    canvasSize?: number,
    inoperable?: boolean,
    doNotUseProportion?: boolean,
    useSmallestIdeal?: boolean,
}

const MAX_CANVAS_SIZE = 1024;


/**
 * 控件抽象类
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
abstract class AbstractControl extends AbstractBase {

    public static readonly namePostfix = 'gui-control-mesh';

    private _proportion = 0.1;
    private _oriWidth = 1;
    private _oriHeight = 1;

    protected breakCamera!: { (): void };

    /**
     * 生成的纹理
     */
    public texture!: AdvancedDynamicTexture;

    /**
     * 原始控件对象
     */
    public control!: Control;

    constructor(name: string, scene: Scene, options?: IAbstractControlOptions) {
        super(name, scene, options);
        this.initialize(name, scene, options || {});
    }

    protected initialize(name: string, scene: Scene, ops: IAbstractControlOptions): void {
        const options = this.options as IAbstractControlOptions;
        this.container = this.initializeMesh(name, scene, ops);
        options.width = ops.width || 1;
        this._oriWidth = options.width;
        options.height = ops.height || 1;
        this._oriHeight = options.height;
        options.left = ops.left || 0;
        options.top = ops.top || 0;
        options.depth = ops.depth || 0;
        options.canvasSize = ops.canvasSize || 64;
        options.doNotUseProportion = !!ops.doNotUseProportion;
        options.useSmallestIdeal = !!ops.useSmallestIdeal;
        this.setMesh();
        this.texture = this.initializeTexture(name, scene, options);
        this.setTexture();
        this.control = this.initializeControl(name, scene, options);
        this.setControl();
        this.alpha = ops.alpha === undefined ? 1 : ops.alpha;
        this.isEnabled = ops.isEnabled === undefined ? true : ops.isEnabled;
        this.display = ops.display === undefined ? true : ops.display;
        this.texture.addControl(this.control);

        this.breakCamera = async (): Promise<void> => {
            const cam = scene.activeCamera;
            if (!cam) return;
            cam.detachControl();
            await Utils.sleep(0);
            if (!cam.inputs.attachedToElement) cam.attachControl();
        };
        this.control.onPointerDownObservable.add(this.breakCamera);

        this.bindEvent(this.control, ops);


        if (ops.renderingGroupId !== undefined) this.renderingGroupId = ops.renderingGroupId;
    }

    public get isControl(): boolean {
        return true;
    }

    public dispose(): void {
        super.dispose();
        this.unbindEvent(this.control);
        this.texture.dispose();
        this.control.dispose();
    }

    /**
     * 设置透明度
     */
    public set alpha(alpha: number) {
        (this.options as IAbstractControlOptions).alpha = alpha;
        this.control.alpha = this.alpha;
    }

    /**
     * 获取透明度
     */
    public get alpha(): number {
        return (this.options as IAbstractControlOptions).alpha!;
    }

    public set isEnabled(enabled: boolean) {
        this.control.isEnabled = enabled;
        if (this.enabledStyle) this.enabledStyle(enabled, this);
    }

    public get isEnabled(): boolean {
        return this.control.isEnabled;
    }

    public set display(display: boolean) {
        (this.container as AbstractMesh).isVisible = display;
    }

    public get display(): boolean {
        return (this.container as AbstractMesh).isVisible;
    }

    public set left(left: number) {
        this.container.position.x = left;
    }

    public get left(): number {
        return this.container.position.x;
    }

    public set top(top: number) {
        this.container.position.y = top;
    }

    public get top(): number {
        return this.container.position.y;
    }

    public set depth(depth: number) {
        this.container.position.z = depth;
    }

    public get depth(): number {
        return this.container.position.z;
    }

    /**
     * 设置宽度
     */
    public set width(width: number) {
        (this.options as IAbstractControlOptions).width = Math.max(width, 0.01);
        this.setMesh();
        this.setTexture();
        this.setControl();
    }

    /**
     * 获取宽度
     */
    public get width(): number {
        return (this.options as IAbstractControlOptions).width!;
    }

    /**
     * 设置高度
     */
    public set height(height: number) {
        (this.options as IAbstractControlOptions).height = Math.max(height, 0.01);
        this.setMesh();
        this.setTexture();
        this.setControl();
    }

    /**
     * 获取高度
     */
    public get height(): number {
        return (this.options as IAbstractControlOptions).height!;
    }

    /**
     * 设置渲染顺序值
     */
    public set renderingGroupId(id: number) {
        if (id === undefined) return;
        (this.container as AbstractMesh).renderingGroupId = id;
    }

    /**
     * 设置不可交互
     */
    public set inoperable(inoperable: boolean) {
        (this.options as IAbstractControlOptions).inoperable = inoperable;
        Helper.getChildMeshes(this.container).forEach(m => m.isPickable = !inoperable);
    }

    public get inoperable(): boolean {
        return (this.options as IAbstractControlOptions).inoperable!;
    }

    /**
     * 获取渲染顺序值
     */
    public get renderingGroupId(): number {
        return (this.container as AbstractMesh).renderingGroupId;
    }

    /**
     * 绑定原始控件事件
     * @param control 原始控件
     * @param options 参数对象
     */
    protected abstract bindEvent(control: Control, options: IAbstractControlOptions): void;

    protected unbindEvent(control: Control): void {
        control.onPointerDownObservable.clear();
    }

    /**
     * 初始化原始控件
     * @param name 名称
     * @param scene 所在场景
     * @param options 参数对象
     * @returns 原始控件
     */
    protected abstract initializeControl(name: string, scene: Scene, options: IAbstractControlOptions): Control;

    /**
     * 初始化控件纹理
     * @param name 名称
     * @param scene 所在场景
     * @param options 参数对象
     * @returns 生成的纹理
     */
    protected initializeTexture(name: string, scene: Scene, options: IAbstractControlOptions): AdvancedDynamicTexture {
        const wp = Math.min(options.canvasSize! * this.width, MAX_CANVAS_SIZE);
        const hp = Math.min(options.canvasSize! * this.height, MAX_CANVAS_SIZE);
        const texture = AdvancedDynamicTexture.CreateForMesh((this.container as AbstractMesh), wp, hp);
        return texture;
    }

    /**
     * 初始化控件网格
     * @param name 名称
     * @param scene 所在场景
     * @param options 参数对象
     * @returns 控件网格
     */
    protected initializeMesh(name: string, scene: Scene, options: IAbstractControlOptions): AbstractMesh {
        const s = 1;
        options.inoperable = !!options.inoperable;
        const mesh = MeshBuilder.CreatePlane(name + '-' + AbstractControl.namePostfix, { width: s, height: s }, scene);
        mesh.isPickable = !options.inoperable;
        const anchor = options.anchor || ANCHOR.CENTER;
        switch (anchor) {
            case ANCHOR.CENTER_TOP:
                mesh.position.set(0, -s * 0.5, 0);
                break;
            case ANCHOR.CENTER_BOTTOM:
                mesh.position.set(0, s * 0.5, 0);
                break;
            case ANCHOR.LEFT_CENTER:
                mesh.position.set(s * 0.5, 0, 0);
                break;
            case ANCHOR.RIGHT_CENTER:
                mesh.position.set(-s * 0.5, 0, 0);
                break;
            case ANCHOR.LEFT_TOP:
                mesh.position.set(s * 0.5, -s * 0.5, 0);
                break;
            case ANCHOR.RIGHT_TOP:
                mesh.position.set(-s * 0.5, -s * 0.5, 0);
                break;
            case ANCHOR.RIGHT_BOTTOM:
                mesh.position.set(-s * 0.5, s * 0.5, 0);
                break;
            case ANCHOR.LEFT_BOTTOM:
                mesh.position.set(s * 0.5, s * 0.5, 0);
                break;
        }
        mesh.bakeCurrentTransformIntoVertices();
        return mesh;
    }

    /**
     * 自动布局
     * @param panel 面板堆栈
     * @param header 页眉
     * @param spacing 留白
     * @param lastControls 原始控件
     * @param controls 原始控件
     */
    protected static autoLayout(panel: StackPanel, header: TextBlock, spacing: number, lastControls: Control[], controls: Control[]): void {
        if (panel.isVertical) {
            header.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
            header.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
            header.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
            header.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
            for (const control of lastControls) {
                control.paddingTop = spacing + 'px';
            }
            for (const control of controls) {
                control.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
                control.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
            }
        } else {
            header.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
            header.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
            header.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
            header.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
            for (const control of lastControls) {
                control.paddingLeft = spacing + 'px';
            }
            for (const control of controls) {
                control.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
                control.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
            }
        }
    }

    /**
     * 设置原始控件
     */
    protected setControl(): void {
        this.control.widthInPixels = this.texture.idealWidth;
        this.control.heightInPixels = this.texture.idealHeight;
    }

    /**
     * 设置控件网格
     */
    private setMesh(): void {
        const options = this.options as IAbstractControlOptions;
        this.container.scaling.set(
            options.doNotUseProportion ? this.width : this.width * this._proportion,
            options.doNotUseProportion ? this.height : this.height * this._proportion,
            1
        );
        this.container.position.set(options.left!, options.top!, BabylonContainer.isRightHand ? -options.depth! : options.depth!);
    }

    /**
     * 设置控件纹理
     */
    private setTexture(): void {
        const options = this.options as IAbstractControlOptions;
        this.texture.idealWidth = Math.min(this.width * options.canvasSize!, MAX_CANVAS_SIZE);
        this.texture.idealHeight = Math.min(this.height * options.canvasSize!, MAX_CANVAS_SIZE);
        this.texture.useSmallestIdeal = options.useSmallestIdeal!;

        const wr = this.width / this._oriWidth;
        const hr = this.height / this._oriHeight;
        this.texture.uScale = wr;
        this.texture.uOffset = (1 - wr) * 0.5;
        this.texture.vScale = hr;
        this.texture.vOffset = (1 - hr) * 0.5;
    }

}


export { AbstractControl };
export type { IAbstractControlOptions };